// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-07-10

package location

import (
	"gitee.com/go-libs/config/src/resource"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

const (
	DefaultBaseDir = "."
	DefaultFolders = "config,../config"
)

type (
	// Location
	// is an interface for user operators.
	Location interface {
		// AddFolder
		// adds a folder to the location manager. Config files will be searched
		// in this folder.
		//
		//   config.Location.Add("config")
		AddFolder(folders ...string) Location

		// Find
		// finds a config file in located folders.
		//
		//   config.Location.Find("app.json")
		//   config.Location.Find("app.yaml", "app.yml")
		Find(names ...string) (resource resource.Resource)

		// GetBaseDir
		// returns the base directory of the application.
		//
		//   return "./"
		//   return "/data/sketch"
		GetBaseDir() (dir string)

		// GetFolders
		// returns the list of folders.
		//
		//   return []string{
		//       "./config",
		//       "../config",
		//   }
		GetFolders() (list []string)

		// SetBaseDir
		// sets the base directory of the application.
		//
		//   config.Location.SetBaseDir("./")
		//   config.Location.SetBaseDir("/data/sketch")
		SetBaseDir(dir string) Location

		// SetFolders
		// sets the list of folders. Override the previous list.
		//
		//   config.Location.SetFolders([]string{
		//       "./config",
		//       "../config",
		//   })
		SetFolders(folders ...string) Location
	}

	location struct {
		baseDir    string
		folderList []string
		mu         *sync.RWMutex
	}
)

// New
// creates a new location manager.
func New() Location {
	return (&location{mu: new(sync.RWMutex)}).
		SetBaseDir(DefaultBaseDir).
		SetFolders(strings.Split(DefaultFolders, ",")...)
}

// AddFolder
// adds a folder to the location manager. Config files will be searched
// in this folder.
func (o *location) AddFolder(folders ...string) Location {
	o.mu.Lock()
	defer o.mu.Unlock()

	// Iterate
	// folder in parameters.
	for _, s := range folders {
		// Ignore empty.
		if s = strings.TrimSpace(s); s == "" {
			continue
		}
		// Unique checker.
		for _, v := range o.folderList {
			if s == v {
				continue
			}
			o.folderList = append(o.folderList, s)
		}
	}
	return o
}

// Find
// finds a config file in located folders.
func (o *location) Find(names ...string) (res resource.Resource) {
	o.mu.RLock()
	defer o.mu.RUnlock()

	// Iterate localed folders and stat it.
	for _, name := range names {
		for _, s := range o.folderList {
			p := filepath.Join(o.baseDir, s, name)
			if info, err := os.Stat(p); err == nil && !info.IsDir() {
				res = resource.Resource(p)
				return
			}
		}
	}

	// Return not found resource.
	res = resource.NotfoundResource
	return
}

// GetBaseDir
// returns the base directory of the application.
func (o *location) GetBaseDir() (str string) {
	o.mu.RLock()
	str = o.baseDir
	o.mu.RUnlock()
	return
}

// GetFolders
// returns the list of folders.
func (o *location) GetFolders() (list []string) {
	o.mu.RLock()
	list = o.folderList
	o.mu.RUnlock()
	return
}

// SetBaseDir
// sets the base directory of the application.
func (o *location) SetBaseDir(dir string) Location {
	o.mu.Lock()
	defer o.mu.Unlock()

	if s, err := filepath.Abs(dir); err == nil {
		o.baseDir = s
	} else {
		o.baseDir = dir
	}
	return o
}

// SetFolders
// sets the list of folders. Override the previous list.
func (o *location) SetFolders(folders ...string) Location {
	o.mu.Lock()
	defer o.mu.Unlock()
	o.folderList = folders
	return o
}
